package service

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"github.com/pkg/errors"
	"github.com/smartwalle/alipay/v3"
	"gorm.io/gorm"
	"pay/api/bmember"
	"pay/api/pay"
	"pay/api/wxpay"
	"pay/internal/conf"
	"pay/internal/constant"
	"pay/internal/model"
	"pay/pkg/ecode"
	"pay/pkg/ecpay"
	"pay/pkg/util"
	"strconv"
	"time"
)

func (s *Service) alipayCreate(order *model.Order, config *model.AlipayRemark,
	token *model.AlipayToken, processRemark *model.OrderProcessRemark) (
	prepayInfo []byte, err error, remoteError error) {
	var alipayResp *alipay.TradeCreateRsp
	amountStr := fmt.Sprintf("%d.%d", order.Amount/100, order.Amount%100)
	alipayReq := alipay.TradeCreate{
		Trade: alipay.Trade{
			NotifyURL: fmt.Sprintf(
				"%s/pay/alipay/callback", conf.Conf.Domain),
			AppAuthToken:   token.Token,
			Subject:        processRemark.Subject,
			OutTradeNo:     order.OrderNumber,
			TotalAmount:    amountStr,
			TimeoutExpress: "2m",
		},
		DiscountableAmount: amountStr,
		BuyerId:            config.OpenId,
		GoodsDetail:        processRemark.AlipayDetails,
	}
	if alipayResp, remoteError = s.downstream.AlipayClient.TradeCreate(alipayReq); remoteError != nil {
		err = ecode.AppendMessage(constant.ErrRemoteError,
			"支付宝调用异常: %s", remoteError.Error())
		return
	}
	if alipayResp.Content.Code == alipay.CodeSuccess {
		if prepayInfo, err = json.Marshal(alipayResp.Content); err != nil {
			return
		}
	} else {
		remoteError = ecode.AppendMessage(constant.ErrRemoteError,
			"支付宝调用失败: %s.%s", alipayResp.Content.Msg, alipayResp.Content.SubMsg)
		return
	}
	return
}

func (s *Service) getAlipayTokenByOrder(ctx context.Context, order *model.Order) (tokenStr string, err error) {
	var token *model.AlipayToken
	alipayRemark := &model.AlipayRemark{}
	if err = json.Unmarshal(order.PayConfig, alipayRemark); err != nil {
		return
	}
	if token, err = s.dao.GetAlipayTokenByID(ctx, alipayRemark.ID); err != nil {
		if errors.Cause(err) == gorm.ErrRecordNotFound {
			err = ecode.AppendMessage(constant.ErrForbiddenAction, "订单支付宝授权已解绑或失效，请重新授权")
		}
		return
	}
	tokenStr = token.Token
	return
}

func (s *Service) alipayQuery(ctx context.Context, order *model.Order) (err error) {
	var token string
	if token, err = s.getAlipayTokenByOrder(ctx, order); err != nil {
		return
	}
	if alipayResp, remoteError := s.downstream.AlipayClient.TradeQuery(alipay.TradeQuery{
		AppAuthToken: token,
		OutTradeNo:   order.OrderNumber,
	}); remoteError != nil {
		err = ecode.AppendMessage(constant.ErrRemoteError,
			"支付宝调用异常: %s", remoteError.Error())
		return
	} else {
		if !alipayResp.IsSuccess() {
			for _, failCode := range constant.AlipayFailedCodes {
				if alipayResp.Content.SubCode == failCode {
					order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
					break
				} else {
					return
				}
			}
		} else {
			switch alipayResp.Content.TradeStatus {
			case "TRADE_SUCCESS", "TRADE_FINISHED":
				// 支付成功
				order.TradeNo = alipayResp.Content.TradeNo
				order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS)
				var amount, mdiscount, pdiscount int64
				if amount, err = util.FromYuanStrToCent(alipayResp.Content.TotalAmount); err != nil {
					return
				}
				if order.Amount != uint64(amount) {
					err = fmt.Errorf("付款金额与返回值不匹配")
					return
				}
				for _, fundDetail := range alipayResp.Content.FundBillList {
					if fundDetail.FundChannel == "MDISCOUNT" {
						if mdiscount, err = util.FromYuanStrToCent(fundDetail.Amount); err != nil {
							return
						}
						order.MerchantDiscount += uint64(mdiscount)
					} else if fundDetail.FundChannel == "DISCOUNT" {
						if pdiscount, err = util.FromYuanStrToCent(fundDetail.Amount); err != nil {
							return
						}
						order.PlatformDiscount += uint64(pdiscount)
					}
				}
				order.UserPayed = order.Amount - order.MerchantDiscount - order.PlatformDiscount
				order.Receive = order.Amount - order.MerchantDiscount
				order.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
			case "TRADE_CLOSED":
				// 支付关闭
				order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
			default:
				return
			}
		}
		order.TradeRemark, _ = json.Marshal(alipayResp.Content)
	}
	return
}

func (s *Service) alipayClose(ctx context.Context, order *model.Order) (err error) {
	var token string
	if token, err = s.getAlipayTokenByOrder(ctx, order); err != nil {
		return
	}
	if alipayResp, remoteErr := s.downstream.AlipayClient.TradeClose(alipay.TradeClose{
		AppAuthToken: token,
		OutTradeNo:   order.OrderNumber,
	}); remoteErr != nil {
		err = ecode.AppendMessage(constant.ErrRemoteError,
			"支付宝调用异常: %s", remoteErr.Error())
		return
	} else {
		if alipayResp.Content.Code != alipay.CodeSuccess {
			err = ecode.AppendMessage(constant.ErrRemoteError,
				"支付宝调用失败: %s.%s", alipayResp.Content.Msg, alipayResp.Content.SubMsg)
			return
		}
	}
	return
}

func (s *Service) alipayRefund(ctx context.Context, order *model.Order, refund *model.Refund) {
	var (
		token    string
		tokenErr error
	)
	if token, tokenErr = s.getAlipayTokenByOrder(ctx, order); tokenErr != nil {
		refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED)
		refund.FailedReason = fmt.Sprintf("支付宝退款失败：授权令牌已过期")
		return
	}
	if alipayResp, remoteError := s.downstream.AlipayClient.TradeRefund(alipay.TradeRefund{
		AppAuthToken: token,
		OutTradeNo:   order.OrderNumber,
		TradeNo:      order.TradeNo,
		OutRequestNo: refund.OrderNumber,
		RefundAmount: fmt.Sprintf("%d.%d", refund.Amount/100, refund.Amount%100),
	}); remoteError != nil {
		refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED)
		refund.FailedReason = fmt.Sprintf("支付宝退款失败：%s", remoteError.Error())
		return
	} else if alipayResp.IsSuccess() {
		// 支付宝成功 => 视为一次性成功
		refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS)
		refund.TradeNo = alipayResp.Content.TradeNo
		refund.TradeRemark, _ = json.Marshal(alipayResp.Content)
		var mdiscount, pdiscount int64
		for _, fundDetail := range alipayResp.Content.RefundDetailItemList {
			if fundDetail.FundChannel == "MDISCOUNT" {
				mdiscount, _ = util.FromYuanStrToCent(fundDetail.Amount)
				refund.MerchantDiscount += uint64(mdiscount)
			} else if fundDetail.FundChannel == "DISCOUNT" {
				pdiscount, _ = util.FromYuanStrToCent(fundDetail.Amount)
				refund.PlatformDiscount += uint64(pdiscount)
			}
		}
		refund.UserPayed = refund.Amount - refund.MerchantDiscount - refund.PlatformDiscount
		refund.Receive = refund.Amount - refund.MerchantDiscount
		if refundTime, err := util.ParseTime(alipayResp.Content.GmtRefundPay); err != nil {
			refund.PayTime = sql.NullTime{Valid: true, Time: refundTime}
		} else {
			refund.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
		}
	} else {
		refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED)
		refund.FailedReason = fmt.Sprintf("支付宝退款失败：%s.%s", alipayResp.Content.Msg, alipayResp.Content.SubMsg)
	}
	return
}

func (s *Service) wechatCreate(
	ctx context.Context, order *model.Order, config *model.WechatRemark,
	processRemark *model.OrderProcessRemark, now time.Time) (
	prepayInfo []byte, err error, remoteError error) {
	var wechatResp *wxpay.JSAPIPreCreateResponse
	wechatReq := &wxpay.JSAPIPreCreateRequest{
		AppId:       model.BootInfo.WechatServiceProviderAppId,
		MchId:       model.BootInfo.WechatServiceProviderMerchantId,
		SubMchId:    config.MerchantId,
		OutTradeNo:  order.OrderNumber,
		Description: processRemark.Subject,
		TimeExpire:  now.Add(time.Minute * 2).Format(util.RFC3339),
		Amount: &wxpay.AmountInfo{
			Total:    int64(order.Amount),
			Currency: "CNY",
		},
		Payer:   &wxpay.Payer{},
		BrandId: strconv.Itoa(int(order.SgId)),
	}
	if config.PStoreId == 0 {
		// 总部订单，需要参与分账
		wechatReq.SettleInfo = &wxpay.SettleInfo{
			ProfitSharing: true,
			SubsidyAmount: int64(order.Amount),
		}
	}
	//switch pay.WechatAccountType(config.AccountType) {
	//case pay.WechatAccountType_WECHAT_PLATFORM:
	//	wechatReq.Payer.OpenId = config.OpenId
	//default:
	//	wechatReq.SubAppId = config.AppId
	//	wechatReq.Payer.SubOpenId = config.OpenId
	//}
	wechatReq.SubAppId = config.AppId
	wechatReq.Payer.SubOpenId = config.OpenId
	if wechatResp, remoteError = s.downstream.TradeCreate(ctx, wechatReq); remoteError != nil {
		return
	}
	if prepayInfo, err = json.Marshal(wechatResp); err != nil {
		return
	}
	return
}

func (s *Service) wechatQuery(ctx context.Context, order *model.Order) (err error) {
	if wechatResp, remoteError := s.downstream.TradeQuery(ctx, &wxpay.TradeQueryRequest{
		BrandId:    strconv.Itoa(int(order.SgId)),
		OutTradeNo: order.OrderNumber,
	}); remoteError != nil {
		return
	} else {
		switch wechatResp.TradeState {
		case "SUCCESS":
			// 支付成功
			order.TradeNo = wechatResp.WxTransId
			order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
			if order.Amount != uint64(wechatResp.Amount.Total) {
				err = fmt.Errorf("付款金额与返回值不匹配")
				return
			}
			order.UserPayed = order.Amount
			order.Receive = order.Amount
			payTime, _ := time.ParseInLocation(
				util.RFC3339, wechatResp.TimeSuccess, util.FixedLocation)
			order.PayTime = sql.NullTime{Valid: true, Time: payTime}
		case "CLOSED", "PAYERROR", "REVOKED":
			// 支付失败
			order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
		default:
			// 未抵达终态时不记录
			return
		}
		order.TradeRemark, _ = json.Marshal(wechatResp)
	}
	return
}

func (s *Service) wechatRefund(ctx context.Context, order *model.Order, refund *model.Refund) {
	if wechatResp, remoteError := s.downstream.TradeRefund(ctx, &wxpay.RefundApplyRequest{
		BrandId:     strconv.Itoa(int(order.SgId)),
		OutTradeNo:  order.OrderNumber,
		OutRefundNo: refund.OrderNumber,
		Reason:      refund.RefundReason,
		Amount:      int64(refund.Amount),
	}); remoteError != nil {
		refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED)
		refund.FailedReason = fmt.Sprintf("微信退款调用失败：%s", remoteError.Error())
		return
	} else {
		refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_REFUNDING)
		refund.TradeNo = wechatResp.RefundId
		refund.TradeRemark, _ = json.Marshal(wechatResp)
	}
	return
}

func (s *Service) createPayByteDance(
	ctx context.Context, order *model.Order, config *model.ByteDanceRemark,
	processRemark *model.OrderProcessRemark) (
	prepayInfo []byte, err error, remoteError error) {
	var byteDanceResp *ecpay.CreateOrderResponse
	byteDanceReq := &ecpay.CreateOrderRequest{
		AppID:       config.ByteDanceAppId,
		OutOrderNo:  order.OrderNumber,
		TotalAmount: int(order.Amount),
		Subject:     processRemark.Subject,
		ValidTime:   120,
		NotifyURL: fmt.Sprintf(
			"%s/pay/bytedance/callback", conf.Conf.Domain),
	}
	if byteDanceResp, remoteError = s.downstream.BytedanceClient.CreateOrder(
		ctx, byteDanceReq); remoteError != nil {
		return
	}
	if prepayInfo, err = json.Marshal(byteDanceResp.OrderInfo); err != nil {
		return
	}
	return
}

func (s *Service) queryPayByteDance(ctx context.Context, order *model.Order) (err error) {
	byteDanceRemark := &model.ByteDanceRemark{}
	if err = json.Unmarshal(order.PayConfig, byteDanceRemark); err != nil {
		return
	}
	if byteDanceResp, remoteError := s.downstream.BytedanceClient.QueryOrder(
		ctx, &ecpay.QueryOrderRequest{
			AppID:      byteDanceRemark.ByteDanceAppId,
			OutOrderNo: order.OrderNumber,
		}); remoteError != nil {
		if byteDanceErr, ok := remoteError.(*ecpay.ErrorResponse); ok {
			err = ecode.AppendMessage(constant.ErrRemoteError,
				"字节跳动调用失败：%s", byteDanceErr.ErrTips)
		}
		return
	} else {
		switch byteDanceResp.PaymentInfo.OrderStatus {
		case "SUCCESS":
			// 支付成功
			order.TradeNo = byteDanceResp.PaymentInfo.ChannelNo
			order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
			if order.Amount != uint64(byteDanceResp.PaymentInfo.TotalFee) {
				err = fmt.Errorf("付款金额与返回值不匹配")
				return
			}
			order.UserPayed = order.Amount
			order.Receive = order.Amount
			if byteDanceResp.PaymentInfo.PayTime == "" {
				order.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
			} else {
				payTime, _ := util.ParseTime(byteDanceResp.PaymentInfo.PayTime)
				order.PayTime = sql.NullTime{Valid: true, Time: payTime}
			}
		case "FAIL":
			// 支付失败
			order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
		default:
			// 未抵达终态时不记录
			return
		}
		order.TradeRemark, _ = json.Marshal(byteDanceResp.PaymentInfo)
	}
	return
}

func (s *Service) refundPayByteDance(ctx context.Context, order *model.Order, refund *model.Refund) {
	byteDanceRemark := &model.ByteDanceRemark{}
	if err := json.Unmarshal(order.PayConfig, byteDanceRemark); err != nil {
		panic(fmt.Sprintf("抖音支付配置数据加载失败"))
		return
	}
	if byteDanceResp, remoteError := s.downstream.BytedanceClient.CreateRefund(ctx, &ecpay.CreateRefundRequest{
		AppID:        byteDanceRemark.ByteDanceAppId,
		OutOrderNo:   order.OrderNumber,
		OutRefundNo:  refund.OrderNumber,
		TotalAmount:  int64(order.Amount),
		RefundAmount: int64(refund.Amount),
		Reason:       refund.RefundReason,
		NotifyURL:    fmt.Sprintf("%s/pay/bytedance/callback", conf.Conf.Domain),
	}); remoteError != nil {
		refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED)
		refund.FailedReason = fmt.Sprintf("抖音退款调用失败：%s", remoteError.Error())
		return
	} else {
		refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_REFUNDING)
		refund.TradeNo = byteDanceResp.RefundNo
		refund.TradeRemark, _ = json.Marshal(byteDanceResp)
	}
	return
}

func (s *Service) doPayZero(ctx context.Context, order *model.Order, now time.Time) (
	resp *pay.DoPayOrderView, err error) {
	// 直接支付成功
	var payMessage json.RawMessage
	order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS)
	order.PayTime = sql.NullTime{Valid: true, Time: now}
	if err = s.dao.Save(ctx, order); err != nil {
		return
	}
	result := formatPayResult(order)
	if payMessage, err = json.Marshal(result); err != nil {
		return
	}
	if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicPayFinish, payMessage); err != nil {
		return
	}
	payType := pay.PayType(order.PayType)
	resp = &pay.DoPayOrderView{
		OrderNumber:   order.OrderNumber,
		Status:        pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS,
		Source:        pay.SourceType(order.SourceType),
		PayType:       payType,
		PayTypeName:   constant.PayNameMap[payType],
		SuccessResult: result,
		PrepayInfo:    constant.EmptyBytes,
	}
	if err = s.dao.DeleteSchedule(ctx, uint8(pay.ScheduleType_SCHEDULE_TYPE_CLOSE), order.ID); err != nil {
		return
	}
	return
}

func (s *Service) doPayMember(ctx context.Context, order *model.Order) (
	resp *pay.DoPayOrderView, err error) {
	if err = s.dao.Transaction(ctx, func(ctx context.Context) (err error) {
		if err = s.dao.LockTradeByOrderNumber(ctx, order, order.OrderNumber); err != nil {
			if errors.Cause(err) == gorm.ErrRecordNotFound {
				err = constant.ErrObjectNotExist
				return
			}
			return
		}
		if pay.PayOrderStatus(order.Status) != pay.PayOrderStatus_PAY_ORDER_STATUS_PAYING {
			err = fmt.Errorf("会员支付发起状态不匹配")
			return
		}
		var (
			memberResp *bmember.BalanceInfoReply
			payMessage json.RawMessage
		)
		if memberResp, err = s.downstream.ConsumeBalance(ctx, order); err != nil {
			return
		}
		switch memberResp.Status {
		case "success":
			order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS)
			order.TradeNo = strconv.Itoa(int(memberResp.BalId))
			if payTime, e := util.ParseTime(memberResp.CreatedAt); e == nil {
				order.PayTime = sql.NullTime{Valid: true, Time: payTime}
			} else {
				order.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
			}
		case "failed":
			order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
		}
		order.TradeRemark, _ = json.Marshal(memberResp)
		if err = s.dao.Save(ctx, order); err != nil {
			return
		}
		result := formatPayResult(order)
		if payMessage, err = json.Marshal(result); err != nil {
			return
		}
		if err = s.dao.PublishUniqueNSQMessage(ctx, constant.TopicPayFinish, payMessage); err != nil {
			return
		}
		payType := pay.PayType(order.PayType)
		resp = &pay.DoPayOrderView{
			OrderNumber:   order.OrderNumber,
			Status:        pay.PayOrderStatus(order.Status),
			Source:        pay.SourceType(order.SourceType),
			PayType:       payType,
			PayTypeName:   constant.PayNameMap[payType],
			SuccessResult: result,
			PrepayInfo:    constant.EmptyBytes,
		}
		return
	}); err != nil {
		return
	}
	return
}

func (s *Service) memberQuery(ctx context.Context, order *model.Order) (err error) {
	var memberResp *bmember.BalanceInfoReply
	if memberResp, err = s.downstream.ConsumeBalance(ctx, order); err != nil {
		return
	}
	switch memberResp.Status {
	case "success":
		order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_SUCCESS)
		order.TradeNo = strconv.Itoa(int(memberResp.BalId))
		if payTime, e := util.ParseTime(memberResp.CreatedAt); e == nil {
			order.PayTime = sql.NullTime{Valid: true, Time: payTime}
		} else {
			order.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
		}
	case "failed":
		order.Status = uint8(pay.PayOrderStatus_PAY_ORDER_STATUS_FAILED)
	}
	order.TradeRemark, _ = json.Marshal(memberResp)
	return
}

func (s *Service) memberRefund(ctx context.Context, order *model.Order, refund *model.Refund) {
	if memberResp, err := s.downstream.RefundBalance(ctx, order, refund); err != nil {
		refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED)
		refund.FailedReason = fmt.Sprintf("会员退款调用失败：%s", err.Error())
		return
	} else {
		switch memberResp.Status {
		case "success":
			refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_SUCCESS)
			refund.TradeNo = strconv.Itoa(int(memberResp.BalId))
			if payTime, e := util.ParseTime(memberResp.CreatedAt); e == nil {
				refund.PayTime = sql.NullTime{Valid: true, Time: payTime}
			} else {
				refund.PayTime = sql.NullTime{Valid: true, Time: time.Now()}
			}
		case "failed":
			refund.Status = uint8(pay.PayOrderStatus_REFUND_ORDER_STATUS_FAILED)
			refund.FailedReason = memberResp.Reason
		}
	}
	return
}
